<?php
// @formatter:off
/**
 * @file WCUser.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-07-18 22:13:05 +0000 (Thu, 18 Jul 2013) $
 *
 * $Id: WCUser.php 97 2013-07-18 22:13:05Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0.0.2/includes/representations/WCUser.php $
 */
// @formatter:on

/**
 * @class WCUser This class represents elements from table %users. It also holds
 * the logic to manage user information.
 */
class WCUser extends WCTableRepresentation implements WCPreviews {
	/**
	 * @var string[] Check WCTableRepresentation class documentation.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"username",
		"password"
	);
	/**
	 * @var string[] Check WCTableRepresentation class documentation.
	 */
	protected $_PropertiesBoolean = array(
		"web_access",
		"sys_access"
	);
	/**
	 * @var string Check WCTableRepresentation class documentation.
	 */
	protected $_PropertiesPrefix = "usr_";
	/**
	 * @var string Check WCTableRepresentation class documentation.
	 */
	protected $_PropertiesTable = "users";
	/**
	 * @var string Check WCTableRepresentation class documentation.
	 */
	protected $_PropertiesNameColumn = "username";
	/**
	 * @var string[int] This property hold the name of profiles assigned to
	 * the user for each group.
	 */
	protected $_profileCode = false;
	/**
	 * @var WCUserData This is the pointer to the extra information of the
	 * user.
	 */
	protected $_data = null;
	//
	// Public methods.
	/**
	 * This method removes "awaiting activation" flags from database.
	 */
	public function activate() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Creating a statement to update flags.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}users\n";
				$query .= "set     usr_hash_register = '',\n";
				$query .= "        usr_hash_dregister = '0'\n";
				$query .= "where   usr_id = :id";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(":id" => $this->id());
			//
			// Updating flags in the database.
			$stmt->execute($params);
			//
			// Reloading representation to avoid issues.
			$this->reload();
		}
	}
	/**
	 * This method allows to get user's avatar.
	 *
	 * @return string This method the assigned avatar for this user.
	 */
	public function avatar() {
		//
		// Setting the default value to be returned.
		$out = "";
		//
		// Loading user's extra information.
		$this->loadData();
		//
		// Checking if this user has a loaded extra information.
		if($this->_data) {
			//
			// Forwarding request.
			$out = $this->_data->avatar();
		}
		//
		// Returning user's avatar.
		return $out;
	}
	/**
	 * This method takes a password hash and check if it matches with current
	 * user password.
	 *
	 * @param string $password Password hash to check.
	 * @return boolean Returns true when the user is ok and the given
	 * password hash matches.
	 */
	public function checkPassword($password) {
		/// @todo ver si esto se puede reemplazar por algo donde no tenga que pasar la
		/// password ya encriptada.
		return $this->ok() && $this->password == $password;
	}
	/**
	 * This method allows to access to the user's extra information.
	 *
	 * @return WCUserData Returns a pointer to the user's extra information.
	 */
	public function & data() {
		//
		// Loading user's extra information.
		$this->loadData();
		//
		// Returns the extra information pointer.
		return $this->_data;
	}
	/**
	 * This is just a dummy method required by the interface WCPreviews. A
	 * user has a preview and it is its avatar, but there's no concept like
	 * generating previews for users.
	 *
	 * @return boolean It always returns false.
	 */
	public function generatePreviews() {
		return false;
	}
	/**
	 * Users have always one preview, therefore this method will always
	 * return true. Such preview is the user's avatar.
	 *
	 * @return boolean It always returns true.
	 */
	public function hasPreview() {
		return true;
	}
	/**
	 * Users have always one preview, but not a list of them, therefore this
	 * method will always return false.
	 *
	 * @return boolean It always returns false.
	 */
	public function hasPreviews() {
		return false;
	}
	/**
	 * This method attempts load a user based on an username and a password
	 * hash.
	 * Also, if the constant WIKI_COMPATIBLE is defined, password are checked
	 * using MediaWiki password systems A and B. For more information about
	 * this visit: http://www.mediawiki.org/wiki/Manual:Resetting_passwords
	 */
	public function loadByAuth($username, $password) {
		//
		// Returning object to its initial state.
		$this->clear();
		//
		// Setting a default value to be returned.
		$ok = true;
		//
		// Checking MediaWiki compatibility
		if(defined("WIKI_COMPATIBLE")) {
			//
			// Creating a statement to request information from
			// database based on given username.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[1]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query .= "from    {$this->_dbprefix}users\n";
				$query .= "where   usr_username = :username";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building a structure to request database information.
			$params = array(":username" => $username);
			//
			// Loading database information.
			if($stmt->execute($params) && $stmt->rowCount() == 1) {
				//
				// Loading user data.
				$row = $stmt->fetch();
				//
				// Creating a shortcut for the current password
				// hash.
				$pass = explode(':', $row['usr_password']);
				//
				// Checking MediaWiki password type.
				switch($pass[1]) {
					case "B":
						//
						// At this pointe, password is a
						// salty one. and a piece of the
						// current one is required to
						// check the given password.
						$password = ":B:{$pass[2]}:".md5("{$pass[2]}-{$password}");
						break;
					case "A":
						//
						// At this point, password only
						// requires a prefix.
						$password = ":A:{$password}";
						break;
					default:
						//
						// At this point, password is not
						// compatible with MediaWiki.
						$ok = false;
						break;
				}
			} else {
				//
				// At this point, there's no user matching given
				// username.
				$ok = false;
			}
		}
		//
		// Checking if it's ok to validate password.
		if($ok) {
			//
			// Creating a statement to load user information using
			// username and password hash.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[2]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query .= "from    {$this->_dbprefix}users\n";
				$query .= "where   usr_username = :username\n";
				$query .= " and    usr_password = :password";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Creating a structure to request database information.
			$params = array(
				":username" => $username,
				":password" => $password
			);
			//
			// Requesting user information
			if($stmt->execute($params) && $stmt->rowCount() == 1) {
				//
				// At this point user is valid and matches
				// username and password hash
				$row = $stmt->fetch();
				//
				// Loading this object with the loaded user.
				$this->load($row["usr_id"]);
			}
		}
		//
		// Returning object status.
		return $this->ok();
	}
	/**
	 * A user is considered to have no issues when WCTableRepresentation::ok()
	 * method returns true and there's no registration hash, which would
	 * mean the user is awaitting registration approval.
	 *
	 * @return boolean Returns the user status.
	 */
	public function ok() {
		return parent::ok() && !$this->hash_register;
	}
	/**
	 * A user's preview is its avatar, therefore, this method will call
	 * method 'avatar()'.
	 *
	 * @return boolean Returns user's avatar.
	 */
	public function preview() {
		return $this->avatar();
	}
	/**
	 * Users have always one preview, but not a list of them, therefore this
	 * method will always return an empty list.
	 *
	 * @return boolean It always returns an empty array.
	 */
	public function previews() {
		return array();
	}
	/**
	 * This method returns the profile name assigned to the user on certain
	 * group.
	 *
	 * @param WCGroup $group This is the group to used as key when searching
	 * for profile codes. If it is not given, value from '$wcGroup' is
	 * assumed. If '$wcGroup' has no value, '$wcDefaults["group"]' is used.
	 * @return string
	 */
	public function profileCode(WCGroup &$group = null) {
		//
		// Loading all profile codes assigned to the current user.
		$this->loadProfileCode();
		//
		// Setting a default value to be returned.
		$out = "ANON";
		//
		// Checking if a group was specified.
		if($group === null) {
			//
			// Loading global pointers.
			global $wcGroup;
			//
			// Checking if '$wcGroup' can be used.
			if($wcGroup) {
				//
				// Using '$wcGroup'.
				$group = $wcGroup;
			} else {
				//
				// Loading global pointers.
				global $wcDefaults;
				global $wcGroupsHolder;
				//
				// Using '$wcDefaults["group"]'.
				$group = $wcGroupsHolder->groupById($wcDefaults["group"]);
			}
		}
		//
		// At this point, there should be a group to look for. If not,
		// default value is returned.
		if($group) {
			//
			// Getting profile's code.
			$out = $this->_profileCode[$group->id];
		}
		//
		// Returning found profile's code.
		return $out;
	}
	/**
	 * This method allows to set password reset flags for the user.
	 *
	 * @return string Returns a hash string that can be used for further
	 * password validations. When error, returns false.
	 */
	public function resetPassword() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Generating a random hash to be used as flag for password reset.
		$hash = "{$this->id()}|".md5(rand(1, 1000) * rand(1, 1000));
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Creating a statement to update password reset flags.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}users\n";
				$query .= "set     usr_hash_reset = :hash,\n";
				$query .= "        usr_hash_dreset = now()\n";
				$query .= "where   usr_id = :id";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":id" => $this->id(),
				":hash" => $hash
			);
			//
			// Attempting to update flags.
			if(!$stmt->execute($params) || $stmt->rowCount() != 1) {
				//
				// At this point, something went wrong, therefore
				// the hash to be return is false.
				$hash = false;
			}
		} else {
			//
			// On Read Only Mode, this hash is always false.
			$hash = false;
		}
		//
		// Returning generated hash.
		return $hash;
	}
	/**
	 * This method allows to get the users RSS hash for not-logged-in RSS
	 * downloads.
	 *
	 * @return string Returns a hash string that can be used for further
	 * RSS validations. On Read Only Method, if there's no hash for the
	 * current user it may return an empty string.
	 */
	public function rssHash() {
		//
		// Checking if there's a hash already generated.
		if(!$this->hash_rss) {
			//
			// Generating a RSS random hash string.
			$hash = "{$this->id()}|".md5(rand(1, 1000) * rand(1, 1000));
			//
			// Saving new hash in the database.
			$this->hash_rss = $hash;
		}
		//
		// Returning user RSS hash.
		return $this->hash_rss;
	}
	/**
	 * This method replaces current user password by another. It also clears
	 * reset password flags.
	 *
	 * @param string $password This is the new password to be set. If it has
	 * to be an encrypted string it must be encrypted before calling this
	 * method.
	 * @return boolean Returns true when the password was successfully
	 * changed.
	 */
	public function setPassword($password) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$ok = true;
		//
		// Checking that:
		//	- Read Only Mode is not activated.
		//	- The current object has no issues.
		//	- And the property to be edited is not a read only property.
		if(!$wcDefaults["read-only"]) {
			//
			// Creating a statement to change password.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}users\n";
				$query .= "set     usr_password    = :password,\n";
				$query .= "        usr_hash_reset  = '',\n";
				$query .= "        usr_hash_dreset = 0\n";
				$query .= "where   usr_id = :id";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":id" => $this->id(),
				":password" => md5($password)
			);
			//
			// Attempting to update password in the database.
			if(!$stmt->execute($params) || $stmt->rowCount() != 1) {
				//
				// At this point something went wrong.
				$ok = false;
			}
			//
			// Reloading to avoid issues.
			$this->reload();
		}
		//
		// Returning modification status.
		return $ok;
	}
	/**
	 * This method allows to know if a user is allowed to use shell tools,
	 * for example CRON.
	 *
	 * @return boolean Returns true when the user is allowed to use shell
	 * processes.
	 */
	public function sysAccess() {
		return $this->sys_access;
	}
	/**
	 * This method allows to access user's property 'username'.
	 *
	 * @return string Returns user's username.
	 */
	public function username() {
		return $this->username;
	}
	/**
	 * This method allows to know if a user is allowed to use the web-site.
	 *
	 * @return boolean Returns true when the user is allowed to use the
	 * web-site.
	 */
	public function webAccess() {
		return $this->web_access;
	}
	// Protected methods.
	/**
	 * This method pushes the object to its initial state before been loaded.
	 */
	protected function clear() {
		$this->_info = array();
		$this->_ok = false;
		$this->_profileCode = false;
		$this->_data = null;
	}
	/**
	 * This method loads all profile codes assigned to a user for all groups.
	 * If there are no configurations save in the database, it generates them
	 * with a default value to avoid further problems.
	 */
	public function loadProfileCode() {
		//
		// Checking this is the first time this operation is called.
		if(!$this->_profileCode) {
			//
			// Loading global pointers.
			global $wcGroupsHolder;
			//
			// Creating an empty list to store profile codes.
			$this->_profileCode = array();
			//
			// Checking if the current user is a root-like user.
			if(in_array($this->id, array(
				WC_USER_ROOT_ID,
				WC_USER_CRON_ID
			))) {
				//
				// Root user doesn't use database for this
				// because the always get ROOT clearance for
				// everything.
				foreach($wcGroupsHolder->groupIds() as $id) {
					$this->_profileCode[$id] = "ROOT";
				}
			} else {
				//
				// Creating a statement to get user's profile
				// codes from the database.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "select  *\n";
					$query .= "from    {$this->_dbprefix}group_users\n";
					$query .= "where   rel_gu_user = :user";

					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Creating a structure for database request.
				$params = array(":user" => $this->id);
				//
				// Asking database configuration.
				if($stmt->execute($params) && $stmt->rowCount() > 0) {
					//
					// Loading each result into the list of
					// profile codes.
					foreach($stmt->fetchAll() as $row) {
						$this->_profileCode[$row["rel_gu_group"]] = $row["rel_gu_profile"];
					}
				}
				//
				// Checking that all groups have a profile
				// configuration for this user.
				foreach($wcGroupsHolder->groups() as $group) {
					//
					// Checking if a default configuration
					// has to be generated
					if(!isset($this->_profileCode[$group->id])) {
						//
						// Adding to the list the profile
						// code 'ANON' which mean
						// 'Anonymous User'. Unless the
						// groups is a public one, in
						// such case, profile code is
						// 'USER' which means 'Regular
						// User'.
						$this->_profileCode[$group->id] = $group->isPublic() ? "USER" : "ANON";
						//
						// Setting this information in
						// the database.
						$this->setDefaultProfile($group);
					}
				}
			}
		}
	}
	//
	// Protected methods.
	/**
	 * This method loads user's extra information. If such information is
	 * not yet present in the database, it is generated with basic data.
	 *
	 * @param boolean $firstTime Because this is a recursive method, this
	 * flag indicates if it's the first iteration.
	 */
	protected function loadData($firstTime = true) {
		//
		// Checking if the extra information is not yet loaded.
		if($this->_data === null) {
			//
			// Loading global pointers.
			global $wcUserDataHolder;
			//
			// Getting the information from the holder.
			$this->_data = $wcUserDataHolder->item($this->id);
			//
			// Checking if the extra information exists.
			if(!$this->_data) {
				//
				// At this point, the information does not
				// exists, therefore is created
				$wcUserDataHolder->create($this->id);
				//
				// Internal pointer is ensure to be clean.
				$this->_data = null;
				//
				// Trying the loading again unless this is not
				// the first try. On second time, this loop
				// brakes to avoid problems.
				if($firstTime) {
					//
					// Re-try.
					$this->loadData(false);
				}
			}
		}
	}
	/**
	 * This method forces a user to have some sort of default privileges in a
	 * group.
	 *
	 * @note This method is not affected by Read Only Mode because it's needed
	 * to keep the system in good health.
	 *
	 * @param WCGroup $group Group where the default privileges must be set.
	 */
	protected function setDefaultProfile(WCGroup &$group) {
		//
		// Creating a statement to insert default privileges.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "insert\n";
			$query .= "        into {$this->_dbprefix}group_users (\n";
			$query .= "              rel_gu_group, rel_gu_user, rel_gu_profile)";
			$query .= "        values(:group, :user, :profile)";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Building database parameters.
		$params = array(
			":group" => $group->id,
			":user" => $this->id,
			":profile" => $group->isPublic() ? "USER" : "ANON"
		);
		//
		// Inserting privileges.
		$stmt->execute($params);
	}
}
?>
